﻿using System.Collections;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Linq;
using static LightCAD.Three.ImageBitmapLoader;

namespace LightCAD.Core
{
    public static class JsonWriterExt
    {
        public static void WriteIdProperty(this Utf8JsonWriter writer, string name, long value)
        {
            writer.WritePropertyName(name);
            writer.WriteNumberValue(value);
        }
        public static void WriteStringProperty(this Utf8JsonWriter writer, string name, string value)
        {
            writer.WritePropertyName(name);
            writer.WriteStringValue(value);
        }
        public static void WriteNumberProperty(this Utf8JsonWriter writer, string name, double value)
        {
            writer.WritePropertyName(name);
            writer.WriteNumberValue(value);
        }
        public static void WriteBoolProperty(this Utf8JsonWriter writer, string name, bool value)
        {
            writer.WritePropertyName(name);
            writer.WriteBooleanValue(value);
        }

        public static void WriteMatrix3dProperty(this Utf8JsonWriter writer, string name, Matrix3d matrix)
        {
            var array = matrix.GetArray();
            writer.WriteArrayProperty(name, array);
        }
        public static void WriteObjectProperty(this Utf8JsonWriter writer, string name, object value, JsonSerializerOptions options)
        {
            writer.WritePropertyName(name);
            JsonSerializer.Serialize(writer, value, options);
        }
        public static void WriteArrayProperty(this Utf8JsonWriter writer, string name, Array array)
        {
            writer.WritePropertyName(name);
            writer.WriteStartArray();
            foreach (var item in array)
            {
                writer.WriteNumberValue((decimal)((double)item));
            }
            writer.WriteEndArray();
        }
        public static void WriteCollectionProperty<T>(this Utf8JsonWriter writer, string name, ICollection<T> collection, JsonSerializerOptions options, Action<Utf8JsonWriter, T> cwriter = null)
        {
            if (collection == null || collection.Count == 0) return;
            writer.WritePropertyName(name);
            if (cwriter == null)
            {
                JsonSerializer.Serialize(writer, collection, options);
            }
            else
            {
                writer.WriteStartArray();
                foreach (T item in collection)
                {
                    cwriter(writer, item);
                }
                writer.WriteEndArray();
            }
        }

        public static void WriteDictionaryProperty<K>(this Utf8JsonWriter writer, string name, IDictionary<string, K> dictionary, JsonSerializerOptions options, Action<Utf8JsonWriter, K> cwriter = null)
        {
            if (dictionary == null || dictionary.Count == 0) return;
            writer.WritePropertyName(name);
            if (cwriter == null)
            {
                JsonSerializer.Serialize(writer, dictionary, options);
            }
            else
            {
                writer.WriteStartObject();
                foreach (var item in dictionary)
                {
                    writer.WritePropertyName(item.Key);
                    cwriter(writer, item.Value);
                }
                writer.WriteEndObject();
            }
        }

        public static void WriteVector2dProperty(this Utf8JsonWriter writer, string name, Vector2d vec)
        {
            writer.WritePropertyName(name);
            writer.WriteStartArray();
            writer.WriteNumberValue(vec.X);
            writer.WriteNumberValue(vec.Y);
            writer.WriteEndArray();
        }

        public static void WriteCurve2dObject(this Utf8JsonWriter writer,ICurve2d curve, JsonSerializerOptions soptions)
        {
            writer.WriteStartObject();
            writer.WriteNumberProperty(nameof(curve.Type), (int)curve.Type);
            Curve2dUtils.WriteProperties(writer, curve, soptions);
            writer.WriteEndObject();
        }
        public static void WriteElementObject(this Utf8JsonWriter writer, LcElement element, JsonSerializerOptions soptions)
        {
            writer.WriteStartObject();
            // ele.WriteExtProperties(writer, soptions);
            element.WriteBaseProperties(writer, soptions);
            element.WriteProperties(writer, soptions);
            writer.WriteEndObject();
        }
        public static void WriteElementSetProperty(this Utf8JsonWriter writer, string name, ElementSet elementSet, JsonSerializerOptions soptions)
        {
            writer.WritePropertyName(name);
            writer.WriteStartArray();
            foreach (LcElement ele in elementSet.Elements)
            {
                writer.WriteElementObject(ele, soptions);
            }
            writer.WriteEndArray();
        }
        public static void WriteBlockObject(this Utf8JsonWriter writer, LcBlock block, JsonSerializerOptions soptions)
        {
            writer.WriteStartObject();
            writer.WriteNumberProperty(nameof(block.Id), block.Id);
            writer.WriteStringProperty(nameof(block.Name), block.Name);
            writer.WriteVector2dProperty(nameof(block.BasePoint), block.BasePoint);

            if (!string.IsNullOrEmpty(block.Description))
                writer.WriteStringProperty(nameof(block.Description), block.Description);
            if (block.AllowAttribute)
                writer.WriteBoolProperty(nameof(block.AllowAttribute), block.AllowAttribute);

            writer.WriteElementSetProperty(nameof(block.Elements), block, soptions);
            writer.WriteEndObject();
        }

    }

    public static class JsonElementExt
    {
        public static bool NullOrUndefined(this ref JsonElement jele)
        {
            return (jele.ValueKind == JsonValueKind.Null || jele.ValueKind == JsonValueKind.Undefined);
        }
        public static string ReadStringProperty(this ref JsonElement jele, string name, bool required = false)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (required && !exist) throw new Exception("errorformat");
            if (!exist || prop.NullOrUndefined()) return default;
            return prop.GetString();
        }
        public static bool? ReadBoolProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;
            return prop.GetBoolean();
        }
        public static int ReadIntProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;
            return prop.GetInt32();
        }
        public static double ReadDoubleProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;
            return prop.GetDouble();
        }
        public static long ReadIdProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;
            return prop.GetInt64();
        }

        public static Matrix3d ReadMatrix3dProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var data = new double[9];
            var arr = prop.EnumerateArray().ToArray();
            for (var i = 0; i < arr.Length; i++)
            {
                data[i] = arr[i].GetDouble();
            }
            return new Matrix3d(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]);
        }
        public static T ReadObjectProperty<T>(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            return JsonSerializer.Deserialize<T>(prop);
        }
        public static ICurve2d ReadCurve2dProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            return prop.ReadCurve2dObject();
        }
        public static List<ICurve2d> ReadCurve2dListProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var list = new List<ICurve2d>();
            var arr = prop.EnumerateArray().ToArray();
            for(var i=0; i<arr.Length; i++)
            {
                var item = arr[i];
                var curve = item.ReadCurve2dObject();
                list.Add(curve);
            }
            return list;
        }
        public static ICurve2d ReadCurve2dObject(this ref JsonElement jele)
        {
            ICurve2d curve;
            var exist = jele.TryGetProperty(nameof(curve.Type), out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var curveType = (Curve2dType)(prop.GetInt32());
            curve = Curve2dUtils.Create(curveType);
            Curve2dUtils.ReadProperties(ref jele, curve);
            return curve;

        }
        public static Vector2d ReadVector2dProperty(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var arr = prop.EnumerateArray().ToArray();
            return new Vector2d(arr[0].GetDouble(), arr[1].GetDouble());
        }

        public static T ReadCollectionProperty<T, K>(this ref JsonElement jele, string name, LcDocument doc) where T : ICollection<K>, new()
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var ktype = typeof(K);
            var eleType = typeof(ElementType);
            var blkType = typeof(LcBlock);
            var objType = typeof(LcObject);

            var arr = prop.EnumerateArray().ToArray();
            var tobj = new T();
            for (var i = 0; i < arr.Length; i++)
            {
                var item = arr[i];
                if (ktype.Equals(eleType))
                {
                    var kobj = JsonSerializer.Deserialize<K>(item);
                    tobj.Add(kobj);
                }
                else if (ktype.Equals(blkType))
                {
                    var blk = (object)item.ReadBlockObject(doc);
                    tobj.Add((K)blk);
                }
                else if (ktype.IsSubclassOf(objType))
                {
                    var kobj = (K)JsonSerializer.Deserialize<K>(item);
                    (kobj as LcObject).Document = doc;
                    tobj.Add(kobj);
                }
            }
            return tobj;
        }
        public static IList<T> ReadListProperty<T>(this ref JsonElement jele, string name)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var list = new List<T>();
            var arr = prop.EnumerateArray().ToArray();
            for (var i = 0; i < arr.Length; i++)
            {
                var item = arr[i];
                var tobj = JsonSerializer.Deserialize<T>(item);
                list.Add(tobj);
            }
            return list;
        }
        public static LcElement ReadElementObject(this ref JsonElement jele, LcDocument doc)
        {
            var typeName = jele.GetProperty("TypeName").GetString();
            var baseType = jele.GetProperty("BaseType").GetString();
            var element = doc.CreateElement(typeName, baseType);
            element.ReadBaseProperties(ref jele);
            element.ReadProperties(ref jele);
            //// ele.WriteExtProperties(writer, soptions);

            return element;

        }
        public static ElementCollection ReadElementSetProperty(this ref JsonElement jele, string name, LcDocument doc)
        {
            var exist = jele.TryGetProperty(name, out JsonElement prop);
            if (!exist || prop.NullOrUndefined()) return default;

            var arr = prop.EnumerateArray().ToArray();
            var eleSet = new ElementCollection();
            for (var i = 0; i < arr.Length; i++)
            {
                var ele = arr[i].ReadElementObject(doc);
                eleSet.Add(ele);
            }
            return eleSet;
        }

        public static LcBlock ReadBlockObject(this ref JsonElement jele, LcDocument doc)
        {
            var block = doc.CreateObject<LcBlock>();
            block.Id = jele.ReadIdProperty(nameof(block.Id));
            block.Name = jele.ReadStringProperty(nameof(block.Name));
            block.BasePoint = jele.ReadVector2dProperty(nameof(block.BasePoint));

            if (jele.TryGetProperty(nameof(block.Description), out JsonElement descrProp))
                block.Description = descrProp.GetString();

            if (jele.TryGetProperty(nameof(block.AllowAttribute), out JsonElement allowAttrProp))
                block.AllowAttribute = allowAttrProp.GetBoolean();

            block.Elements = jele.ReadElementSetProperty(nameof(block.Elements), doc);
            return block;
        }
    }
    //public class ElementStyleConverter : JsonConverter<ElementStyle>
    //{
    //    public override ElementStyle? Read(JsonElement jele, Type typeToConvert, JsonSerializerOptions options)
    //    {
    //        var str = jele.GetString();
    //        return ElementStyle.Parse(str);
    //    }

    //    public override void Write(Utf8JsonWriter writer, ElementStyle value, JsonSerializerOptions options)
    //    {
    //        writer.WriteStringValue(value.ToString());
    //    }
    //}

    public class Vector2dConverter : JsonConverter<Vector2d>
    {

        public override Vector2d Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            reader.Read();
            var x = reader.GetDouble();

            reader.Read();
            var y = reader.GetDouble();

            reader.Read();
            return new Vector2d(x, y);
        }

        public override void Write(Utf8JsonWriter writer, Vector2d value, JsonSerializerOptions options)
        {
            writer.WriteStartArray();
            writer.WriteNumberValue(value.X);
            writer.WriteNumberValue(value.Y);
            writer.WriteEndArray();
        }
    }
    //public class DoubleConverter : JsonConverter<double>
    //{
    //    public override double Read(JsonElement jele, Type typeToConvert, JsonSerializerOptions options)
    //    {
    //        var str = jele.GetString();
    //        return double.Parse(str);
    //    }

    //    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    //    {
    //        throw new NotImplementedException();
    //    }

    //    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    //    {
    //        writer.WriteStringValue(value.ToString("0.00"));
    //    }
    //}
}
